/* global window */
const { spawn } = require('child_process');
const fs = require('fs');
const tmp = require('tmp');
const conf = require('./conf');

const outputFormats = {
  asciidoc: 'text/plain',
  context: 'application/x-latex',
  epub: 'application/epub+zip',
  epub3: 'application/epub+zip',
  latex: 'application/x-latex',
  odt: 'application/vnd.oasis.opendocument.text',
  pdf: 'application/pdf',
  rst: 'text/plain',
  rtf: 'application/rtf',
  textile: 'text/plain',
  docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
};

const highlightStyles = [
  'pygments',
  'kate',
  'monochrome',
  'espresso',
  'zenburn',
  'haddock',
  'tango',
];

const readJson = (str) => {
  try {
    return JSON.parse(str);
  } catch (e) {
    return {};
  }
};

exports.generate = (req, res) => {
  let pandocError = '';
  const outputFormat = Object.prototype.hasOwnProperty.call(outputFormats, req.query.format)
    ? req.query.format
    : 'pdf';
  new Promise((resolve, reject) => {
    tmp.file({
      postfix: `.${outputFormat}`,
    }, (err, filePath, fd, cleanupCallback) => {
      if (err) {
        reject(err);
      } else {
        resolve({
          filePath,
          cleanupCallback,
        });
      }
    });
  }).then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
    const options = readJson(req.query.options);
    const metadata = readJson(req.query.metadata);
    const params = [];

    params.push('--pdf-engine=xelatex');
    params.push('--webtex=http://chart.apis.google.com/chart?cht=tx&chf=bg,s,FFFFFF00&chco=000000&chl=');
    if (options.toc) {
      params.push('--toc');
    }
    options.tocDepth = parseInt(options.tocDepth, 10);
    if (!Number.isNaN(options.tocDepth)) {
      params.push('--toc-depth', options.tocDepth);
    }
    options.highlightStyle = highlightStyles.includes(options.highlightStyle) ? options.highlightStyle : 'kate';
    params.push('--highlight-style', options.highlightStyle);
    Object.keys(metadata).forEach((key) => {
      params.push('-M', `${key}=${metadata[key]}`);
    });

    let finished = false;

    function onError(error) {
      finished = true;
      cleanupCallback();
      reject(error);
    }

    const format = outputFormat === 'pdf' ? 'latex' : outputFormat;
    params.push('-f', 'json', '-t', format, '-o', filePath);
    const pandoc = spawn(conf.values.pandocPath, params, {
      stdio: [
        'pipe',
        'ignore',
        'pipe',
      ],
    });
    let timeoutId = setTimeout(() => {
      timeoutId = null;
      pandoc.kill();
    }, 50000);
    pandoc.on('error', onError);
    pandoc.stdin.on('error', onError);
    pandoc.stderr.on('data', (data) => {
      pandocError += `${data}`;
    });
    pandoc.on('close', (code) => {
      if (!finished) {
        clearTimeout(timeoutId);
        if (!timeoutId) {
          res.statusCode = 408;
          cleanupCallback();
          reject(new Error('timeout'));
        } else if (code) {
          cleanupCallback();
          reject();
        } else {
          res.set('Content-Type', outputFormats[outputFormat]);
          const readStream = fs.createReadStream(filePath);
          readStream.on('open', () => readStream.pipe(res));
          readStream.on('close', () => cleanupCallback());
          readStream.on('error', () => {
            cleanupCallback();
            reject();
          });
        }
      }
    });
    req.pipe(pandoc.stdin);
  }))
  .catch((err) => {
    console.error(err);
    const message = err && err.message;
    if (message === 'unauthorized') {
      res.statusCode = 401;
      res.end('Unauthorized.');
    } else if (message === 'timeout') {
      res.statusCode = 408;
      res.end('Request timeout.');
    } else {
      res.statusCode = 400;
      res.end(pandocError || 'Unknown error.');
    }
  });
};
